package com.niconicod.ofb.utils.flightmodel;

import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.niconicod.ofb.utils.PhysicsUtils;


public class PhysicalFlightModel extends FlightModel {

    private Body body;
    private float thrustForce;
    private float turningForce;
    private float length;
    private float maximalTurningAngle;

    public PhysicalFlightModel(Vector2 position, Vector2 startNoseDirection, Body body, float thrustForce, float turningForce, float length, float maximalTurningAngle) {
        super(position, startNoseDirection);
        this.body = body;
        this.thrustForce = thrustForce;
        this.turningForce = turningForce;
        this.length = length;
        this.maximalTurningAngle = maximalTurningAngle;
    }

    public void act(float delta) {
        applyThrust();
        applyRotation();
        applyDragForce();
        applyStabilization();
    }

    private void applyThrust() {
        Vector2 appliedThrustForce = new Vector2(getNoseDirection());
        appliedThrustForce.scl(thrustLevel * thrustForce);
        body.applyForceToCenter(appliedThrustForce, true);
    }

    private void applyRotation() {
        if (rudderLevel != 0) {
            Vector2 rotationForceApplicationPoint = getTailForcesApplicationPoint();
            Vector2 appliedRotationForce = getNoseDirection().scl(turningForce);
            float rotationForceAngle = Math.signum(rudderLevel) * -90f;
            appliedRotationForce.rotateDeg(rotationForceAngle);

            body.applyForce(appliedRotationForce, rotationForceApplicationPoint, true);
        }
    }

    private void applyDragForce() {
        Vector2 appliedDragForce = new Vector2();

        float airDensity = PhysicsUtils.AIR_DENSITY;
        float dragCoefficient = PhysicsUtils.DRAG_COEFFICIENT;
        float v = (float) Math.pow(body.getLinearVelocity().len(), 2);

        float dragForce = airDensity * dragCoefficient * v * 0.5f;
        float dragAngle = body.getLinearVelocity().angleDeg();
        appliedDragForce.set(dragForce, 0);
        appliedDragForce.rotateDeg(dragAngle);
        appliedDragForce.scl(-1);

        body.applyForceToCenter(appliedDragForce, true);
    }

    private void applyStabilization() {
        Vector2 stabilizationForceApplicationPoint = getTailForcesApplicationPoint();
        Vector2 appliedStabilizationForce = getNoseDirection().scl(turningForce);

        float noseDeviationAngle = getNoseDirection().angleDeg(body.getLinearVelocity());

        if (noseDeviationAngle > 180) {
            noseDeviationAngle = noseDeviationAngle - 360;
        }

        float stabilizationForceAngle = Math.signum(noseDeviationAngle) * 90f;
        float stabilizationForceScale = Math.abs(noseDeviationAngle / maximalTurningAngle);
        if (stabilizationForceScale > 1.1f) {
            stabilizationForceScale = 1.1f;
        }

        appliedStabilizationForce.scl(stabilizationForceScale);

        appliedStabilizationForce.rotateDeg(stabilizationForceAngle);

        body.applyForce(appliedStabilizationForce, stabilizationForceApplicationPoint, true);
    }

    public Body getBody() {
        return body;
    }

    public float getThrustLevel() {
        return thrustLevel;
    }

    public void setThrustLevel(float thrustLevel) {
        this.thrustLevel = thrustLevel;
    }

    public float getRudderLevel() {
        return rudderLevel;
    }

    public void setRudderLevel(float rudderLevel) {
        this.rudderLevel = rudderLevel;
    }

    public static PhysicalFlightModel createDefault(Body body, float length) {
        return new PhysicalFlightModel(body.getPosition(), Vector2.Y, body, 2000, 200, length, 45);
    }

    public static PhysicalFlightModel createInstance(Body body, Vector2 noseDirection, float length, float thrustForce, float turningForce, float maximalTurningAngle) {
        return new PhysicalFlightModel(body.getPosition(), noseDirection, body, thrustForce, turningForce, length, maximalTurningAngle);
    }

    public Vector2 getNoseDirection() {
        Vector2 direction = new Vector2(startNoseDirection);
        direction.rotateRad(body.getAngle());
        return direction;
    }

    @Override
    public float getAngleDeg() {
        return body.getAngle() * MathUtils.radiansToDegrees;
    }

    public Vector2 getTailForcesApplicationPoint() {
        return getNoseDirection().scl(-length / 2).add(body.getPosition());
    }
}
